package com.autumn.platform.web.boot;

import java.util.Arrays;
import java.util.List;

import javax.servlet.DispatcherType;

import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Role;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.autumn.platform.core.boot.CoreAutoConfiguration;
import com.autumn.platform.core.context.reqeust.IRequestLog;
import com.autumn.platform.web.bind.provider.PropertyValuesProviders;
import com.autumn.platform.web.exceptionresolver.ExceptionHandlerMapping;
import com.autumn.platform.web.exceptionresolver.ExceptionResolverControllerAdvice;
import com.autumn.platform.web.jsonbody.JsonBodyControllerAdvice;
import com.autumn.platform.web.jsonbody.JsonBodyHttpMessageConverter;
import com.autumn.platform.web.jsonbody.JsonCustomizer;
import com.autumn.platform.web.request.CompositeHttpSessionIdResolver;
import com.autumn.platform.web.request.ISessionInvalidateListener;
import com.autumn.platform.web.request.RequestContextFilter;
import com.autumn.platform.web.resulthandler.context.HandlerMethodContextHolder;
import com.autumn.platform.web.resulthandler.wrapper.ResultWrapper;
import com.autumn.platform.web.resulthandler.wrapper.impl.ExceptionResultWrapper;
import com.autumn.platform.web.resulthandler.wrapper.impl.MapResultWrapper;
import com.autumn.platform.web.resulthandler.wrapper.impl.PageResultWrapper;
import com.autumn.platform.web.resulthandler.wrapper.impl.ResultWrapperComposite;

import net.sf.uadetector.service.UADetectorServiceFactory;

@Configuration
@EnableConfigurationProperties({WebProperties.class, ServerProperties.class})
@AutoConfigureAfter(CoreAutoConfiguration.class)
@AutoConfigureBefore(ValidationAutoConfiguration.class)
public class WebAutoConfiguration implements WebMvcConfigurer, ApplicationContextAware {

    private final MessageSource messageSource;

    private final WebProperties webProperties;

    private final ServerProperties serverProperties;

    public WebAutoConfiguration(MessageSource messageSource, WebProperties webProperties, ServerProperties serverProperties) {
        super();
        this.messageSource = messageSource;
        this.webProperties = webProperties;
        this.serverProperties = serverProperties;

        if (webProperties.isDisabledAutoUpdateUserAgent()) {
            UADetectorServiceFactory.getOnlineUpdatingParser().shutdown();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        HandlerMethodContextHolder.setApplicationContext(applicationContext);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 解决IE中返回JSON变下载的问题
        MappingJackson2HttpMessageConverter mc = new MappingJackson2HttpMessageConverter();
        mc.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN));
        converters.add(0, mc);

        JsonBodyHttpMessageConverter converter = new JsonBodyHttpMessageConverter();
        converter.setJsonContentType(MediaType.TEXT_HTML);
        converters.add(0, converter);

        //        ByteArrayHttpMessageConverter bc = new ByteArrayHttpMessageConverter();
        //        converters.add(bc);
        //
        //        StringHttpMessageConverter sc = new StringHttpMessageConverter();
        //        sc.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN));
        //        converters.add(sc);

    }

    @Bean
    @ConditionalOnMissingBean(ExceptionHandlerMapping.class)
    public ExceptionHandlerMapping exceptionHandlerMapping() {
        return new ExceptionHandlerMapping();
    }

    /**
     * 校验信息国际化配置
     * 
     * @return
     */
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @ConditionalOnMissingBean(javax.validation.Validator.class)
    public LocalValidatorFactoryBean defaultValidator() {
        OptionalValidatorFactoryBean factoryBean = new OptionalValidatorFactoryBean();
        factoryBean.setValidationMessageSource(messageSource);
        return factoryBean;
    }

    /**
     * 请求信息过滤器
     * 
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(RequestContextFilter.class)
    public FilterRegistrationBean<RequestContextFilter> autumnRequestContextFilter(
            @Autowired(required = false) List<ISessionInvalidateListener> sessionInvalidateListeners,
            @Autowired(required = false) List<IRequestLog> requestLogs) {
        FilterRegistrationBean<RequestContextFilter> filter = new FilterRegistrationBean<RequestContextFilter>();
        filter.setName("autumnRequestContextFilter");
        //org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter
        filter.setOrder(FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER - 95);

        filter.addUrlPatterns(new String[] {"/*"});
        filter.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[] {DispatcherType.FORWARD});
        filter.setAsyncSupported(true);

        RequestContextFilter requestContextFilter = new RequestContextFilter();
        if (null != sessionInvalidateListeners && !sessionInvalidateListeners.isEmpty()) {
            requestContextFilter.setSessionInvalidateListeners(sessionInvalidateListeners);
        }
        if (null != requestLogs && !requestLogs.isEmpty()) {
            requestContextFilter.setRequestLogs(requestLogs);
        }
        filter.setFilter(requestContextFilter);
        return filter;
    }

    @Bean
    @ConditionalOnMissingBean(CorsFilter.class)
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.addExposedHeader(CompositeHttpSessionIdResolver.TOKEN_HEADER_NAME);
        source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
        bean.setOrder(FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER - 96);
        return bean;
    }

    @Bean
    @ConditionalOnMissingBean(WebMvcRegistrations.class)
    public WebMvcRegistrations webMvcRegistrations() {
        return new WebMvcRegistrations();
    }

    @Bean
    @ConditionalOnMissingBean(JsonCustomizer.class)
    public JsonCustomizer jsonCustomizer() {
        return new JsonCustomizer();
    }

    /**
     * JSON序列化之前的对象包装器
     * 
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(name = "exceptionResultWrapper")
    public ResultWrapper exceptionResultWrapper() {
        return new ExceptionResultWrapper();
    }

    @Bean
    @ConditionalOnMissingBean(name = "pageResultWrapper")
    public ResultWrapper pageResultWrapper() {
        PageResultWrapper wrapper = new PageResultWrapper();
        if (null != webProperties && null != webProperties.getPageProperties()) {
            wrapper.setPageProperties(webProperties.getPageProperties());
        }
        return wrapper;
    }

    @Bean
    @ConditionalOnMissingBean(name = "mapResultWrapper")
    public ResultWrapper mapResultWrapper() {
        return new MapResultWrapper();
    }

    /**
     * 结果包装器
     * 
     * @return
     */
    @Primary
    @Bean
    @ConditionalOnMissingBean(ResultWrapperComposite.class)
    public ResultWrapperComposite resultWrapperComposite() {
        return new ResultWrapperComposite();
    }

    /**
     * 绑定参数时属性提供器
     * 
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(PropertyValuesProviders.class)
    public PropertyValuesProviders propertyValuesProviders() {
        return new PropertyValuesProviders();
    }

    @Bean
    @ConditionalOnMissingBean(ExceptionResolverControllerAdvice.class)
    public ExceptionResolverControllerAdvice exceptionResolverControllerAdvice() {
        return new ExceptionResolverControllerAdvice();
    }

    @Bean
    @ConditionalOnMissingBean(JsonBodyControllerAdvice.class)
    public JsonBodyControllerAdvice jsonBodyControllerAdvice() {
        return new JsonBodyControllerAdvice();
    }

    /**
     * 会话策略
     * 
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(HttpSessionIdResolver.class)
    public CompositeHttpSessionIdResolver httpSessionStrategy() {
        CompositeHttpSessionIdResolver strategy = new CompositeHttpSessionIdResolver();
        return strategy;
    }

    //    @Bean
    //    @ConditionalOnMissingBean(SessionEventHttpSessionListenerAdapter.class)
    //    @ConditionalOnBean(HttpSessionListener.class)
    //    public SessionEventHttpSessionListenerAdapter SessionEventHttpSessionListenerAdapter(List<HttpSessionListener> listeners) {
    //        return new SessionEventHttpSessionListenerAdapter(listeners);
    //    }

    /**
     * 将HTTP请求转发到HTTPS
     * 
     * @return
     */
    @Bean
    @ConditionalOnProperty("server.ssl.enabled")
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint constraint = new SecurityConstraint();
                constraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                constraint.addCollection(collection);
                context.addConstraint(constraint);
            }
        };
        tomcat.addAdditionalTomcatConnectors(httpConnector());
        return tomcat;
    }

    private Connector httpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        //Connector监听的http的端口号
        int httpPort = this.webProperties.getHttpPortToHttps();
        if (0 == httpPort) {
            httpPort = 80;
        }
        connector.setPort(httpPort);
        connector.setSecure(false);
        //监听到http的端口号后转向到的https的端口号
        int port = this.serverProperties.getPort();
        if (0 == port) {
            port = 443;
        }
        connector.setRedirectPort(port);
        return connector;
    }
}
